"""
JAKE ELLOWITZ -- jellowitz@clarku.edu
"""

try:
  import pygame
  from pygame.locals import QUIT, K_ESCAPE
  #from pygame.locals import *
  has_pygame = True
except ImportError:
  print 'Could not import pygame.'
  has_pygame = False

import random
import sys
from numpy import pi, log, sin, cos, arctan2, sqrt
import time
from vec2d import vec

from interface import wrapper, reward
from sharing import share_policy

# THe brain selection
import mc_brain
import ran_brain
import it_brain
import ql_brain
import ql_bad_brain
import ql_bolt_brain

import data

### Default parameters
run_time = 1000
n_av = 25

output = 'graph-coll'
output_filename = '../data/default'
graphics = False
#graphics = True

sharing = 'none'
share_temp = 0.5

sel_brain = ql_bolt_brain

num_bots        = 10
v_min           = 8.0
v_max           = 8.0
omega_min       = -0.15*pi
omega_max       = 0.15*pi
dt              = 0.2
bot_radius      = 10 
search_buffer   = bot_radius
search_buffer   = bot_radius/10.
search_radius   = bot_radius + search_buffer
# Boundaries
# Changing the boundary geometry will require a reworking of:
#    check_wall, pygame surface. (Those are all that immediately come to mind)
left_wall       = 0.0
right_wall      = 150
bottom_wall     = 0.0
top_wall        = 150
# For indexing the bot and collision matrix: which rank 2 
# indexes contain which values
BAC_ii_bot = 0
BAC_ii_type = 1
# State of the robot when collision happened
BAC_ii_angle = 2
# for the RL wrapper
n_angles = 4
n_actions = n_angles
# For quadtree:
# Default max depth: Must be able to fit at least 2 bots in.
# Thus we solve: length/(2^depth) >= 4R
# But note it must fit in the minimum dimension
# This also only works if the bots are approximately evenely distributed because
# of the dynamic positioning of the quadtree divisions
default_max_depth = int(log(\
                        min((top_wall-bottom_wall,right_wall-left_wall))\
                        /bot_radius)/log(2)) - 2

n_pacman_anim = 15
pacman = True

def update_from_rad (bot_radius):
  
  search_buffer   = bot_radius/10.
  search_radius   = bot_radius + search_buffer
  default_max_depth = int(log(\
                          min((top_wall-bottom_wall,right_wall-left_wall))\
                          /bot_radius)/log(2)) - 2

default_limit = 5
# For the clock; fps is regulated inside the rendering function (7/17/08)
max_fps = 35

# Make sure we don't have too many bots
max_den = 1.0/2.0
if 4.0*num_bots*bot_radius*bot_radius > max_den*right_wall*top_wall:
  print 'Too many bots. Reduce bot number or increase arena.'
  sys.exit ()

class check_env:
  """
  Key Lists:
  (1) move_candidates:
        [ [[which bot, collision type]],
          distance moved,
          potential new_x,
          potential new_y ]
        We calculate new_x, new_y based on the point of closest approach
        between the bot and the collision object such that they are just
        touching. Then we go through all of these such events which can
        occur during the bots movement step, and take the shortest one, which
        signifies the earliest collision, hence the only one that is physically
        reasonable. This is useful for calculating the realistic position of
        the bot during a move step with a collision.
  (2) collisions:
        [ which_bot,
          collision type,
          angle of collision w.r.t. the bot's move direction ]
      This provides a list of nearby surfaces (within the search radius) of 
      the bot. This is useful to calculate the state of the sensors i.e. the
      state of the bot.
  (3) event:
        [ which_bot, collision_type ]
      This is the first element in move_candidate, except this is cropped
      to correspond to the accepted shortest move.
  """

  def wall_col (self, bot, x, y):
    """
    Inputs: bot, x, y (x, y typically the anticipated move)
    Outputs: a list of potential new locations. THe smallest movement will 
    be chosen (the pool includes bot collisions)
    There could be many wall angle of collisions, so this is a list. 
		If there is a collision, position bot against colliding object
    """
    move_candidates = []
    # The distance vector, or the proposed vector between the final
    # and initial position of the bot during this step
    d = vec(x - bot.x, y - bot.y)
    # Inverted normal vectors corresponding to each wall. Used to
    # find the location of closest approach.
    n_right = vec(1., 0.)
    n_left = vec(-1., 0.)
    n_top = vec(0., 1.)
    n_bottom = vec(0., -1.)
    # Make sure we're moving towards the wall (i.e. along the inverted
    # normal vector). Also check if we intersect the wall.
    if d.dot(n_right) > 0.\
    and bot.x + d.dot(n_right) > right_wall - bot_radius:
      # After working through the linear algebra
      delta_x = right_wall - bot_radius - bot.x
      delta_y = delta_x*d.y/d.x
      move_dist = sqrt(delta_x**2 + delta_y**2)
      move_candidates += [[[[bot, 'wall']], move_dist, delta_x + bot.x, delta_y + bot.y]]
    if d.dot(n_top) > 0.\
    and bot.y + d.dot(n_top) > top_wall - bot_radius:
      delta_y = top_wall - bot_radius - bot.y
      delta_x = delta_y*d.x/d.y
      move_dist = sqrt(delta_x**2 + delta_y**2)
      move_candidates += [[[[bot, 'wall']], move_dist, delta_x + bot.x, delta_y + bot.y]]
    if d.dot(n_left) > 0.\
    and bot.x - d.dot(n_left) < left_wall + bot_radius:
      delta_x = left_wall + bot_radius - bot.x
      delta_y = delta_x*d.y/d.x
      move_dist = sqrt(delta_x**2 + delta_y**2)
      move_candidates += [[[[bot, 'wall']], move_dist, delta_x + bot.x, delta_y + bot.y]]
    if d.dot(n_bottom) > 0.\
    and bot.y - d.dot(n_bottom) < bottom_wall + bot_radius:
      delta_y = bottom_wall + bot_radius - bot.y
      delta_x = delta_y*d.x/d.y
      move_dist = sqrt(delta_x**2 + delta_y**2)
      move_candidates += [[[[bot, 'wall']], move_dist, delta_x + bot.x, delta_y + bot.y]]
    return move_candidates

  def bots_col (self, bots, bot, x, y):
    """
    Find which bots that collided
    Inputs: bots (typically nearby, from quad),
      bot, x, y (x, y typically the anticipated move)
    outputs: potential move candidates.
		If there is a collision, position bot against colliding object
    """
    move_candidates = []
    for i in xrange (len (bots)):
      # Find the distance between the bot and those nearby
      if (bots[i].x - x)**2 + (bots[i].y - y)**2 < 4.0*bot_radius**2\
      and bots[i] != bot:
       # Find the closest approach of the two centers
        # See http://www.gamasutra.com/features/20020118/vandenhuevel_02.htm
        bot_distance_sq = (bot.x - bots[i].x)**2 + (bot.y - bots[i].y)**2
        D = (x-bot.x)*(bots[i].x-bot.x) + (y-bot.y)*(bots[i].y - bot.y)
        F = bot_distance_sq - D**2
        # Find the distance T short of the end placement x,y
        T = 4.*bot_radius**2 - F
        # Only accept smallest move, i.e. min length correction
        temp_len_corr = D - sqrt(T)
        # First entry is bots in the collision
        in_col = [[bot, 'bot']]
        if bots[i].move_state == 'forward':
          in_col += [[bots[i], 'bot']]
        move_candidates += [[in_col,\
                             temp_len_corr,\
                             temp_len_corr*cos(bot.phi) + bot.x,\
                             temp_len_corr*sin(bot.phi) + bot.y]]
    return move_candidates

  def check_touching_wall (self, bot):
    """
    Simple nearby wall checking routine, used to check state of bot w.r.t. walls.
    Inputs: bot
    Outputs: touching list (see class comments)
    """
    touching = []
    if bot.x > right_wall - search_radius:
      touching_angle = (0.*pi - bot.phi) % (2*pi)
      touching += [[bot, 'wall', touching_angle]]
    if bot.y > top_wall - search_radius:
      touching_angle = (pi/2. - bot.phi) % (2*pi)
      touching += [[bot, 'wall', touching_angle]]
    if bot.x < left_wall + search_radius:
      touching_angle = (1.*pi - bot.phi) % (2*pi)
      touching += [[bot, 'wall', touching_angle]]
    if bot.y < bottom_wall + search_radius:
      touching_angle = (3.*pi/2. - bot.phi) % (2*pi)
      touching += [[bot, 'wall', touching_angle]]
    return touching

  def check_touching_bots (self, bots, bot):
    """
    Simple nearby bot checking, used to check state of bot w.r.t. bots
    Inputs: nearby_bots (ones close enough to check see quad_tree), bot
    Ouputs: touching list (see class comments)
    """
    touching = []
    for nearby_bot in bots:
      d = vec(nearby_bot.x - bot.x, nearby_bot.y - bot.y)
      if d.mag_sq () < (bot_radius + search_radius)**2\
      and nearby_bot != bot:
        raw_angle = arctan2 (d.y, d.x) % (2*pi)
        touching_angle = (raw_angle - bot.phi) % (2*pi)
        touching += [[bot, 'bot', touching_angle]]
    return touching
  
  def check_touching (self, bots, bot):
    """
    Compounds the touching lists from bots and walls.
    Inputs: nearby_bots (ones close enough to check see quad_tree), bot
    Ouputs: touching list (see class comments)
    """
    touching = []
    touching += self.check_touching_wall (bot)
    touching += self.check_touching_bots (bots, bot)
    return touching

  def all_col (self, bots, bot, x, y):
    """
    Check all types of collisions; 7/17 has walls and bots.
    Inputs: nearby bots, see quad_tree, potential next x,y (for collision detection)
      bot, x, y (x, y typically anticipated move)
    Outputs: Events (see class comments)
    """
    events = []
    move_candidates = []
    
    move_candidates_temp = self.bots_col (bots, bot, x, y)
    move_candidates += move_candidates_temp
    
    move_candidates_temp = self.wall_col (bot, x, y)
    move_candidates += move_candidates_temp
   
    # For calculaqting the distance the bot moves
    xi, yi = bot.x, bot.y

    min_move = 10000000.
    for move in move_candidates:
      if move[1] < min_move:
        min_move = move[1]
        bot.x = move[2]
        bot.y = move[3]
        # Note events can contain more than one element (if two bots
        # collide)
        events = move[0]

    # For calculaqting the distance the bot moves
    bot.dist += sqrt((bot.x-xi)**2 + (bot.y-yi)**2)

    return events

class event_handler:
  """
  key lists:
  (1) bots_states_actions_rewards:
        [ which_bot,
          old state (last state),
          action corresponding to last state,
          reward resulting from the state, action pair,
          new state as a result of last action ]

  The point of this class is to go through the events, gather outcome
  information, and pass that information to the wrapper which in turn 
  will give us an action, to pass back to the wrapper to execute that
  action on our bot.
  """

  def __init__ (self, dis, col):
    self.chk = check_env ()
    self.rew = reward ()
    self.wra = wrapper (v_min, v_max, omega_min, omega_max)
    self.sha = share_policy ()
    self.dis = dis 
    self.col = col

  def get_event_outcome (self, events):
    """
    Go through 'events' and find event information for event[0] (the bot).
    Event infomration is bots_state_actions_rewards (see class comments). 
    This also tracks the distance for the bots between events. Time tracked
    in parse_events. (time between events).

    WE IDENTIFY COLLISIONS HERE, and count them in our data.

    Inputs: events
    Outputs: bsar
    """
    bots_states_actions_rewards = []
    bot_counted = []
    for event in events:
      # Because bots can collide head on, we don't want redundant collision
      # information, therefore we take our events and find each singular
      # collison. Note we have already cropped out all but the minimal
      # collision distance. Realistically the ONLY collision which can
      # occur is the minimum distance one.
      if event[0] not in bot_counted:
        # Track the distance the bot moved; dist is updated while moving, 
        # dist_last is updated since the last event
        d = event[0].dist - event[0].last_dist
        reward = 0.
        reward += self.rew.move_reward (d)
        reward += self.rew.rotate_reward (event[0].omega*d)
        touching = self.chk.check_touching (event[0].nearby_bots, event[0])
        # Old state from before current_state calculation
        old_state = event[0].state
        # The action resulting from that state. Note  this corresponds to
        # same state incidence, allowing us to find Q(s,a)
        old_action = event[0].action
        # Get numerical state to be used later in brain
        current_state = self.wra.state_conversion (event[0], touching, n_angles, BAC_ii_angle)
        # Need to update this to correspond to the next action, chosen in
        # parse events
        event[0].state = current_state
        bots_states_actions_rewards += [[event[0], old_state, old_action, reward, current_state]]
        # Don't count same bot twice
        bot_counted += [event[0]]
        
        #BETA
        # Keep track of previous total distance so we can find the
        # difference later (useful when calculating rewards)
        event[0].last_dist = event[0].dist

        # BETA Identify collisions
        # If we hit a wall, log it. If we hit a bot, log it.
        if event[1] == 'wall':
          event[0].wall_coll += 1
        elif event[1] == 'bot':
          event[0].bot_coll += 1


    return bots_states_actions_rewards
    bots_states_actions_rewards


  def parse_events (self, bots_states_actions_rewards, t):
    """
    Go through bsar and get corresponding actions to new state.
    Following this, execute the action using the wrapper. 
    Inside here is the brain/policy iteration base don the events.
    This also tracks the time of the bots in between events. Distance
    tracked in get_event_outcome. (distance traveled between events)
    inputs: bsar
    outputs: none

    Simulation data is extrapolated in this function.
    Sharing occurs in this function
    """
    for bsar in bots_states_actions_rewards:
      bot_temp = bsar[0]
      old_state_temp = bsar[1]
      old_action_temp = bsar[2]
      reward_temp = bsar[3]
      curr_state_temp = bsar[4]

      # Get our next action and perform updates on the policy
      action_temp = bot_temp.bra.iterate (old_state_temp, old_action_temp, reward_temp, curr_state_temp)
      
      # BETA: bot sharing
#      try:
      if sharing != 'none':
        share_bots = self.sha.check_touching_bots_share (bot_temp.nearby_bots, bot_temp, bot_radius, search_radius)
        for sb in share_bots:
          if self.sha.policy_share_test (bot_temp, sb, share_temp):
            if sharing == 'full':
              sb.bra.q = list (bot_temp.bra.q)
              sb.bra.v = list (bot_temp.bra.v)
              sb.bra.policy = list (bot_temp.bra.policy)
            else:
              print 'Error in sharing policy.'
#      except:
#      print 'Error in sharing. Perhaps the brain doesn\'t fit the criterion.'

      bot_temp.action = action_temp
      
      bot_temp.t = t
      # BETA append distance and collision tracking i.e. gather data from event.
      self.dis.av_dist (bot_temp.dist, t, t-bot_temp.t_last, bot_temp.v)
      self.col.av_coll (bot_temp.wall_coll, bot_temp.bot_coll,\
                        t, t-bot_temp.t_last)

      bot_temp.t_last = bot_temp.t
 
      # Now peform the action
      self.wra.action_conversion (bot_temp, action_temp, n_angles, n_actions)


class move:  
  # We separate the algorithm from its interaction with the bot
  # This way algorithm replacement is easier
  def __init__ (self, dis, col):
    """
    Everything necessary to move the bot. If there is a collision, it
    is logged as an 'event', which is passed to the event handler. THe
    event handler is one step closer to interfacing with each bot's brain.
    """
    self.chk = check_env ()
    self.eve = event_handler (dis, col)
  
  def quad_tree (self, bots, limit=default_limit, max_depth=default_max_depth):
    """
    A recursive functional quadtree search algorithm. 
    Inputs: Bots (limit, max_depth as well)
    Max depth must be able to fit at least 2 radii, thus we dynamically determine it
    Outputs: None, but it appends all bots in relevant search quadrants
    for each bot. THis means that the bot.nearby_bots must be reset eslewhere
    each step.
    """
    # THe bots here is scaled down
    if len(bots) <=limit or max_depth == 0:
      # THis is the leaf part
      for bot in bots:
        bot.nearby_bots += bots
    else:
      # This is a branch
      quad = [[],[],[],[]]
      pivot = [0.0, 0.0]
      # Divide screen by weighting where the average bot position is.
      for bot in bots:
        pivot[0] += bot.x ; pivot[1] += bot.y
      pivot[0] *= 1.0/len(bots)
      pivot[1] *= 1.0/len(bots)
      for bot in bots:
        max_move = dt*(bot.v + v_max) # both bots can move during the update!
        if bot.x - (bot_radius + max_move) < pivot[0]:
          if bot.y - (bot_radius + max_move) < pivot[1]:
            quad[0].append (bot)
          if bot.y + (bot_radius + max_move) > pivot[1]:
            quad[1].append (bot)
        if bot.x + (bot_radius + max_move) > pivot[0]:
          if bot.y - (bot_radius + max_move) < pivot[1]:
            quad[2].append (bot)
          if bot.y + (bot_radius + max_move) > pivot[1]:
            quad[3].append (bot)
      # Divides the branches and searches each recursively
      self.quad_tree (quad[0], limit, max_depth-1)
      self.quad_tree (quad[1], limit, max_depth-1)
      self.quad_tree (quad[2], limit, max_depth-1)
      self.quad_tree (quad[3], limit, max_depth-1)


  def euler (self, x, y, phi, omega, v, dt=dt):
    """
    Calculates next orientation/spot. Liinear so Euler algorithm is exact.
    Careful changing this movement algorithm, some of the simulation assumes it.
    Inputs: x, y, phi, t, omega, v, dt
    Outputs: x, y, phi, t
    """
    # Changing this will require a change in the move checkign for rotation
    # Note that the way the simulation is designed, only (x,y) OR phi get changed.
    x   += v*cos(phi)*dt
    y   += v*sin(phi)*dt
    phi += omega*dt
    phi = phi % (2*pi)
    return x, y, phi
 
  def move_a_bot (self, nearby_bots, bot, t):
    """
    7/17
    Updates the orientation specific to a bot instance. 
    Gathers the events list; see event handler The algorithm is as follows:
    (a) Generate a proposed new spot
    (b) If there is an event, handle it according to its type
          Note: collision events (in move_state forward) handled differently
          than done rotating events: clip to collision object as opposed to
          rotation angle
    (c) If no outstanding events, accept the position change.
    Inputs: nearby bots (see quad_tree), center bot
    Outputs: none
    """
    collisions = []
    events = []
    # Generate the proposed location prior to collision/done rotation
    # checking
    temp_x, temp_y, temp_phi\
      = self.euler (bot.x, bot.y, bot.phi, bot.omega, bot.v, dt)
    if bot.move_state == 'forward':
      # Get next coordinates
      events += self.chk.all_col (nearby_bots, bot, temp_x, temp_y)
    elif bot.move_state == 'rotate':
      # Different type of event hadled differently than collisions (all_col)
      # According to the Euler algorithm, within this band is the closest
      # we ever get to the final angle without overdoing a rotation
      if abs(bot.phi - bot.phif) <= abs(dt*bot.omega):
        events += [[bot, 'done_rotate']]
        bot.phi = bot.phif
        
    if events:
      bsar = self.eve.get_event_outcome (events)
      # Now compute the result of the events
      self.eve.parse_events (bsar, t)
    else:
      bot.x, bot.y, bot.phi = temp_x, temp_y, temp_phi
      bot.dist += bot.v*dt
 
  def move_all_bots (self, bots, t):
    """
    Iterates through all bots, finds nearby bots, then goes back through
    all bots and tests for movements, and gathers collisions.
    Inputs: bots
    Outputs: None, but gathers collisions
    """
    for bot in bots:
      # If we don't reset this, the list gets very large. It must be
      # cleared before it is appended to in the quad_tree recursion algorithm
      bot.nearby_bots = []
    # Find nearby bots
    self.quad_tree (bots)
    for bot in bots:
      #bot.t = t
      self.move_a_bot (bot.nearby_bots, bot, t)

  
class bot:

  def __init__ (self, bots, sel_brain):
    self.init (bots, sel_brain)

  def init (self, bots, sel_brain):
    """
    6/30
    Needs bots input to determine where to place new instant (checks all other
    bots for overlaps).
    Contains all information pertaining to a bot. Many of these represent many
    individual bots. When initiated it places the bot randomly. The class contains
    a brain. This brain is accessed by external functions when relevant events 
    need to update the policy, etc.
    """
    self.move_state = 'forward'
    bot.nearby_bots = []
    self.v = v_max
    self.t = 0.
    self.t_last = 0.
    self.omega = 0.0
    self.phif = 0.0
    self.dist = 0.0
    self.last_dist = 0.0
    self.wall_coll = 0
    self.bot_coll = 0
    # Below is used to check to position of the random placement
    chk_temp = check_env ()
    while True:
      self.randomize_pos ()
      if not chk_temp.check_touching (bots, self):
        # we placed it so we can get rid of chk
        del chk_temp
        break
    
    # Default values here. These are update immediately on step 1 of the
    # simulation.
    self.state = 0
    self.action = 0
    # And we also need a brain for each bot
    self.bra = sel_brain.brain (2**n_angles, n_angles)

    self.pacman_iteration = random.randint (0, n_pacman_anim-1)

  def randomize_pos (self):
    """
    Gets a new set of location parameters
    """
    self.x = random.random()*(right_wall - left_wall)
    self.y = random.random()*(top_wall - bottom_wall)
    self.phi = 2*pi*random.random ()
  
def get_input ():
  """
  6/30
  Tests for keyboard or exit interrups. When one of the exit calls is
  flagged the program terminates. Perhaps later other keyboard or mouse
  controls will be implemented.
  """
  keystate = pygame.key.get_pressed()
  for event in pygame.event.get():
    if event.type == QUIT or keystate[K_ESCAPE]:
      return True
  return False


def make_icon ():
  """
  Draws the tray icon; no need to bother changing this.
  """
  icon = pygame.Surface ((32,32))
  icon.set_colorkey ((0,0,0)) # make black transparent
  icon.fill ((0,0,0))
  pygame.draw.circle (icon, (255,255,255), (16,16), 16)
  pygame.draw.line (icon, (0,0,0), (16,16), (32,16), 3)
  pygame.display.set_icon (icon)

def draw_mouth (surface, bot):
  bot.pacman_iteration += 1
  # drawing the mouth
  for i in xrange (0, int (n_pacman_anim*sin(pi*bot.pacman_iteration*0.05))):
    pygame.draw.line (surface, (0,0,0), (int(bot.x),int(bot.y)),\
                     (int(bot.x+bot_radius*cos(bot.phi+i*pi/50)),int(bot.y+bot_radius*sin(bot.phi+pi/16))),\
                     int(bot_radius/5))
    pygame.draw.line (surface, (0,0,0), (int(bot.x),int(bot.y)),\
                     (int(bot.x+bot_radius*cos(bot.phi-i*pi/50)),int(bot.y+bot_radius*sin(bot.phi-pi/16))),\
                     int(bot_radius/5))
  # this is the eye
  pygame.draw.circle (surface, (0,0,0),\
                      (int(bot.x + 2*bot_radius/3.*cos(bot.phi-pi/2.)),\
                       int(bot.y + 2*bot_radius/3.*sin(bot.phi-pi/2.))),\
                       int(bot_radius/5.))

  

def draw (surface, time, bots, clock, font):
  """
  Renders the screen. Draws the background, each bot (and its orientation),
  and also finds the FPS and prints that. As a result of this, we require a 
  pygame.font handle, and a clock handle to count and display the fps.
  Inputs: surface, bots, clock, font
  Outputs: None, renders screen.  
  """
  # Set background to black / clear screen
  surface.fill((0,0,0))
  # Draw each bot
  for bot in bots:
    if pacman:
      pygame.draw.circle (surface, (255,255,0), (int(bot.x),int(bot.y)), int(bot_radius))
    else:
      pygame.draw.circle (surface, (255,255,255), (int(bot.x),int(bot.y)), int(bot_radius))
    
    # Also draw the line indicating direction
    pygame.draw.line (surface, (0,0,0), (int(bot.x),int(bot.y)),\
                     (int(bot.x+bot_radius*cos(bot.phi)),int(bot.y+bot_radius*sin(bot.phi))),\
                     int(bot_radius/5))
    if pacman:
      draw_mouth (surface, bot)
  
  # Draw fps; tick to get fps reference and to regulate fps
  clock.tick (max_fps)
  fps_handle = font.render (str(int(clock.get_fps())) + ' FPS', False, (255,0,0)) 
  surface.blit (fps_handle, (10,10))
  time_handle = font.render ('T = '+str(int(time)), False, (255,0,0))
  surface.blit (time_handle, (10,25))
  
  # Update the whole screen
  pygame.display.flip ()

def time_info (t, t_max, m, m_max):
  # Show time and trial number
  # first clear screen
  clr = ''
  for z in xrange (0, 80):
    clr += ' '
  print '\r%s' % clr,
  val = '\rtime = '+str(int(t))+'/'+str(int(t_max))+' :: m = '+str(m) + '/' + str(m_max)
  sys.stdout.write (val)
  sys.stdout.flush ()

def run ():

  if has_pygame:
    pygame.init ()
    make_icon ()
    
  
    if graphics:
      # Requred for rendering
      # Draw the icon
      # Get handles on what we need to find and render FPS
      c = pygame.time.Clock ()
      font = pygame.font.SysFont('Courier New', 16, True, False)

      # Get a surface to render on
      surface = pygame.display.set_mode((int(right_wall), int(top_wall)))
      pygame.display.set_caption ('Intelligent Bot Simulation')
  
  ### EXTERNAL TO RE-INIT
  dis = data.distance_tracker ()
  col = data.collision_tracker ()
  mov = move (dis, col)
  # Initialize/generate the bots
  bots = []
  for i in xrange (0,num_bots):
    bots.append (bot (bots, sel_brain))
  ###

  br = False
  for i in xrange (0,n_av):
    ### INTERNAL TO RE-INIT
    for j in xrange (0,num_bots):
      bots[j].init (bots, sel_brain)
    t = 0.
    ###

    # THe infinite iterating program loop
    # 0 means run forever
    while t<run_time or run_time==0:
    #while True:
      t += dt
      
      if has_pygame:
        if get_input ():
          br = True
          # takes us out of the first level loop
          break
     
      mov.move_all_bots (bots, t)
     
      if has_pygame:
        if graphics:
          draw (surface, t, bots, c, font)
      
      time_info (t, run_time, i+1, n_av)

    # This takes us out of the second-level loop.
    if br: break
  
  if has_pygame:
    pygame.quit ()
  if output == 'graph-dist':
    dis.graph_av_distance ()
  if output == 'graph-coll':
    col.graph_colls ()
  if output == 'file':
    data.file_out (output_filename, dis, col)

if __name__=='__main__':
  run ()
